package logger

import (
	"bytes"
	"context"
	"fmt"
	"strings"
	"sync"
	"time"
)

type LogLevel int

const TraceName = "X-OR-TRACE-ID"
const LevelColorSetClear = "\033[0m"
const maxLogSyncLine = 1000
const (
	_oddNumberErrMsg    = "Ignored key without a value."
	_nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys."
)

const (
	// different colors log text:
	_            = iota + 30 // black
	ColorRed                 // red
	ColorGreen               // green
	ColorYellow              // yellow
	ColorBlue                // blue
	ColorMagenta             // magenta
	_                        // cyan
	ColorWhite               // white
)

// LogType
const (
	TextType = "text"
	JsonType = "json"
)

// MessageLevel
const (
	NOTSET   = iota
	DEBUG    = LogLevel(10 * iota) // DEBUG = 10
	INFO     = LogLevel(10 * iota) // INFO = 20
	NOTICE   = LogLevel(10 * iota) // INFO = 30
	WARNING  = LogLevel(10 * iota) // WARNING = 40
	ERROR    = LogLevel(10 * iota) // ERROR = 50
	CRITICAL = LogLevel(10 * iota) // CRITICAL = 60
)

// LevelColorFlag, MessageLevel color flag.
var LevelColorFlag = []string{
	DEBUG:    colorSet(ColorWhite, 0),
	INFO:     colorSet(ColorGreen, 0),
	NOTICE:   colorSet(ColorBlue, 0),
	WARNING:  colorSet(ColorYellow, 0),
	ERROR:    colorSet(ColorRed, 1),
	CRITICAL: colorSet(ColorMagenta, 1),
}

var LevelString = map[LogLevel]string{
	DEBUG:    "DEBUG",
	INFO:     "INFO",
	NOTICE:   "NOTICE",
	WARNING:  "WARNING",
	ERROR:    "ERROR",
	CRITICAL: "CRITICAL",
}

func colorSet(l LogLevel, way int) string {
	return fmt.Sprintf("\033[%d;%dm", way, LogLevel(l))
}

// Logger main logger struct
type Logger struct {
	Level         LogLevel
	Record        *Record
	Format        *Message
	StreamHandler *StreamMessageHandler
	SyncInterval  int
	useTencentCls bool
	ctx           context.Context
}

// loggerFileMap
type loggerFileMap struct {
	LoggerMap map[string]*Logger
}

// loggerFileChan
type loggerFileChan struct {
	LogMsg  []byte
	LogFile string
}

const MaxWarnLogFileCount = 20

var loggerFiles loggerFileMap
var logMsgChan chan loggerFileChan
var mutex sync.Mutex

func (log *loggerFileMap) newLogFile(fileName string, loggerHandler *Logger) *Logger {
	mutex.Lock()
	defer mutex.Unlock()

	if loggerHandler == nil {
		if handler, ok := log.LoggerMap[fileName]; ok {
			return handler
		}
		return nil
	}
	if log.LoggerMap == nil || len(log.LoggerMap) == 0 {
		log.LoggerMap = make(map[string]*Logger, 0)
	}

	log.LoggerMap[fileName] = loggerHandler
	if len(log.LoggerMap) >= MaxWarnLogFileCount {
		loggerHandler.Warning("The log files is too more, it will reduced system performance")
	}
	return loggerHandler
}

func (log *loggerFileMap) closeAll() {
	mutex.Lock()
	defer mutex.Unlock()

	for k, item := range log.LoggerMap {
		item.StreamHandler.LogFileHandler.Close()
		delete(log.LoggerMap, k)
	}
}

// New get logger struct
func New(level LogLevel, logType string, logDir string, syncInterval int) *Logger {
	if logMsgChan == nil {
		logMsgChan = make(chan loggerFileChan, 1000)
	}
	logDir = strings.TrimRight(logDir, "/")
	stream := &StreamMessageHandler{
		LogDir:      logDir,
		LogFileName: "default",
		LogExt:      ".log",
		LogBuffer:   bytes.NewBuffer([]byte{}),
	}

	if logDir != "" {
		stream.LogFilePath = fmt.Sprintf("%s/%s%s", stream.LogDir, stream.LogFileName, stream.LogExt)
	}
	stream.LogFileHandler = openLogFile(stream.LogFilePath)

	if syncInterval > 0 {
		go func() {
			for {
				time.Sleep(time.Duration(syncInterval) * time.Millisecond)
				stream.SyncLog()
			}
		}()
	}

	loggerHandler := &Logger{
		Level: level,
		Format: &Message{
			FormatType: logType,
		},
		StreamHandler: stream,
		SyncInterval:  syncInterval,
		ctx:           context.TODO(),
	}
	loggerFiles.newLogFile(stream.LogFileName, loggerHandler)

	go func() {
		for {
			select {
			case logData := <-logMsgChan:
				// logFileItem defalut.log or other logfile
				logFileItem := loggerFiles.newLogFile(logData.LogFile, nil)
				if logFileItem == nil {
					logFileItem = loggerFiles.newLogFile(stream.LogFileName, nil)
				}
				if logFileItem == nil {
					return
				}

				logFileItem.StreamHandler.Write(logData.LogMsg)
				if loggerHandler.SyncInterval == 0 {
					go logFileItem.StreamHandler.SyncLog()
				}
			}
		}
	}()

	return loggerHandler
}

// log, sed message to handler.
func (l *Logger) log(level LogLevel, format string, a ...interface{}) {
	if level < l.Level {
		return
	}

	l.Record = GetMessageRecord(l.ctx, level, format, a...)
	l.Record.LogFile = l.StreamHandler.LogFileName
	if l.StreamHandler != nil {
		logMsgChan <- loggerFileChan{
			LogFile: l.StreamHandler.LogFileName,
			LogMsg:  l.Format.GetMessage(l.Record),
		}
		dispatchLog(l.Record)
	}
}

// logw, sed message to handler with kv format.
func (l *Logger) logw(level LogLevel, message string, kv ...interface{}) {
	if level < l.Level {
		return
	}

	l.Record = GetMessageKVRecord(l.ctx, level, message, kv...)
	l.Record.LogFile = l.StreamHandler.LogFileName
	if l.StreamHandler != nil {
		logMsgChan <- loggerFileChan{
			LogFile: l.StreamHandler.LogFileName,
			LogMsg:  l.Format.GetMessage(l.Record),
		}
		dispatchLog(l.Record)
	}
}

// dispatchLog 分发日志
func dispatchLog(record *Record) {
	sendKafkaLog(record)
	sendClsChan(record)
}

// LogDeferClose close handler
func (l *Logger) LogDeferClose() {
	l.StreamHandler.SyncLog()
	loggerFiles.closeAll()
}

// LogFile, change default log file name.
func (l *Logger) LogFile(fileName string) *Logger {
	logFileItem := loggerFiles.newLogFile(fileName, nil)
	if logFileItem != nil {
		return logFileItem
	}
	stream := &StreamMessageHandler{
		LogFileName: fileName,
		LogExt:      l.StreamHandler.LogExt,
		LogDir:      l.StreamHandler.LogDir,
		LogBuffer:   bytes.NewBuffer([]byte{}),
	}

	if l.StreamHandler.LogDir != "" {
		stream.LogFilePath = fmt.Sprintf("%s/%s%s", stream.LogDir, stream.LogFileName, stream.LogExt)
	}
	stream.LogFileHandler = openLogFile(stream.LogFilePath)
	newHandler := &Logger{
		Level:         l.Level,
		Format:        l.Format,
		StreamHandler: stream,
		SyncInterval:  l.SyncInterval,
	}
	if l.SyncInterval > 0 {
		go func() {
			for {
				stream.SyncLog()
				time.Sleep(time.Duration(l.SyncInterval) * time.Millisecond)
			}
		}()
	}

	loggerFiles.newLogFile(fileName, newHandler)

	return newHandler
}

// Debug, record DEBUG message.
func (l *Logger) Debug(format string, a ...interface{}) {
	l.log(DEBUG, format, a...)
}

// Info, record INFO message.
func (l *Logger) Info(format string, a ...interface{}) {
	l.log(INFO, format, a...)
}

// Notice, record INFO message.
func (l *Logger) Notice(format string, a ...interface{}) {
	l.log(NOTICE, format, a...)
}

// Warning, record WARNING message.
func (l *Logger) Warning(format string, a ...interface{}) {
	l.log(WARNING, format, a...)
}

// Error, record ERROR message.
func (l *Logger) Error(format string, a ...interface{}) {
	l.log(ERROR, format, a...)
}

// Critical, record CRITICAL message.
func (l *Logger) Critical(format string, a ...interface{}) {
	l.log(CRITICAL, format, a...)
}

// Debugw, record DEBUG message with kv format
func (l *Logger) Debugw(message string, kv ...interface{}) {
	l.logw(DEBUG, message, kv...)
}

// Infow, record INFO message with kv format.
func (l *Logger) Infow(message string, kv ...interface{}) {
	l.logw(INFO, message, kv...)
}

// Noticew, record INFO message with kv format.
func (l *Logger) Noticew(message string, kv ...interface{}) {
	l.logw(NOTICE, message, kv...)
}

// Warningw, record WARNING message with kv format.
func (l *Logger) Warningw(message string, kv ...interface{}) {
	l.logw(WARNING, message, kv...)
}

// Errorw, record ERROR message with kv format.
func (l *Logger) Errorw(message string, kv ...interface{}) {
	l.logw(ERROR, message, kv...)
}

// Criticalw, record CRITICAL message with kv format.
func (l *Logger) Criticalw(message string, kv ...interface{}) {
	l.logw(CRITICAL, message, kv...)
}
